﻿using System;
using System.ComponentModel;
using System.Linq;
using System.Xml;
using Notoric.Model.Collections;
using Notoric.Model.Internal;
using Notoric.Model.Packaging;
using Notoric.Model.Validation;

namespace Notoric.Model.Elements.CWNotation
{
    /// <summary>
    /// Presents a <see cref="ChordElement"/> in a Common Western Music Notation.
    /// </summary>
    public class Chord : DurationalSymbol
    {
        #region Constructors

        /// <summary>
        /// Creates a new <see cref="Chord"/> instance.
        /// </summary>
        /// <param name="packagePart">
        /// <para>
        /// Type: <see cref="XmlPackagePart"/>
        /// </para>
        /// <para>
        /// The package part that owns this element.
        /// </para>
        /// </param>
        /// <param name="owner">
        /// <para>
        /// Type: <see cref="Staff"/>
        /// </para>
        /// <para>
        /// The model element that directly owns this element.
        /// </para>
        /// </param>
        /// <param name="presentedElement">
        /// <para>
        /// Type: <see cref="ChordElement"/>
        /// </para>
        /// <para>
        /// The chord element presented by this chord.
        /// </para>
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// Any of the arguments is <see langword="null"/>.
        /// </exception>
        internal Chord(XmlPackagePart packagePart, Staff owner, ChordElement presentedElement)
            : base(packagePart, owner, presentedElement)
        {
            if (presentedElement == null)
                throw new ArgumentNullException("presentedElement");

            presentedElement.Tones.CollectionChanged += new EventHandler<NotifyCollectionChangedEventArgs>(Tones_CollectionChanged);
            presentedElement.PropertyChanged += new PropertyChangedEventHandler(presentedElement_PropertyChanged);

            notesCollection = new ElementCollection<Note>(this, notesElement);
            notes = new ReadOnlyCollection<Note>(notesCollection);

            var currentEnvironment = FindOwner<Staff>().GetEnvironmentFor(this);

            foreach (ToneElement tone in presentedElement.Tones)
            {
                var note = new Note(PackagePart, this, tone, currentEnvironment);
                note.PropertyChanged += new PropertyChangedEventHandler(note_PropertyChanged);
                notesCollection.Add(note);
            }

            LayoutStem();
            LayoutNotes();
            LayoutDots();

            this.PropertyChanged += new PropertyChangedEventHandler(Chord_PropertyChanged);
        }       

        /// <summary>
        /// Creates a new <see cref="Chord"/> instance.
        /// </summary>
        /// <param name="packagePart">
        /// <para>
        /// Type: <see cref="XmlPackagePart"/>
        /// </para>
        /// <para>
        /// The package part that owns this element.
        /// </para>
        /// </param>
        /// <param name="owner">
        /// <para>
        /// Type: <see cref="Staff"/>
        /// </para>
        /// <para>
        /// The model element that directly owns this element.
        /// </para>
        /// </param>
        /// <param name="element">
        /// <para>
        /// Type: <see cref="XmlElement"/>
        /// </para>
        /// <para>
        /// The XML element that holds the data of this object.
        /// </para>
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// Any of the arguments is <see langword="null"/>.
        /// </exception>
        internal Chord(XmlPackagePart packagePart, Staff owner, XmlElement element)
            : base(packagePart, owner, element)
        {
            notesElement = element[PackageVocabulary.NoteElementName + "s", PackageVocabulary.PackageNamespaceUri];

            notesCollection = new ElementCollection<Note>(this, notesElement);
            notes = new ReadOnlyCollection<Note>(notesCollection);
            foreach (var note in notesCollection)
                note.PropertyChanged += new PropertyChangedEventHandler(note_PropertyChanged);

            stemDirectionAttribute = element.Attributes[PackageVocabulary.StemDirectionAttribute];
            stemLengthAttribute = element.Attributes[PackageVocabulary.StemLengthAttribute];

            ((ChordElement)PresentedElement).Tones.CollectionChanged += new EventHandler<NotifyCollectionChangedEventArgs>(Tones_CollectionChanged);
            ((ChordElement)PresentedElement).PropertyChanged += new PropertyChangedEventHandler(presentedElement_PropertyChanged);

            this.PropertyChanged += new PropertyChangedEventHandler(Chord_PropertyChanged);
        }

        #endregion

        #region CoreSymbol Members

        /// <inheritdoc/>
        public override void ProcessEnvironmentChange(Environment environment)
        {
            lockNotesCollection = true;

            foreach (var note in notesCollection)
                note.ProcessEnvironmentChange(environment);

            lockNotesCollection = false;

            LayoutStem();
            LayoutNotes();
            LayoutDots();
        }

        #endregion

        #region DurationalSymbol Members

        /// <inheritdoc/>
        protected override XmlElement CreateSymbolElementCore(XmlDocument document)
        {
            var element = document.CreateElement(
                PackageVocabulary.PackageNamespacePrefix,
                PackageVocabulary.ChordElementName,
                PackageVocabulary.PackageNamespaceUri
                );

            notesElement = document.CreateElement(
                PackageVocabulary.PackageNamespacePrefix,
                PackageVocabulary.NoteElementName + "s",
                PackageVocabulary.PackageNamespaceUri
                );

            stemDirectionAttribute = document.CreateAttribute(PackageVocabulary.StemDirectionAttribute);
            stemLengthAttribute = document.CreateAttribute(PackageVocabulary.StemLengthAttribute);

            element.Attributes.Append(stemDirectionAttribute);
            element.Attributes.Append(stemLengthAttribute);

            element.AppendChild(notesElement);

            return element;
        }

        /// <inheritdoc/>
        public override double GetCoreWidth()
        {
            return GetSizes(ChordPart.All);
        }

        #endregion

        #region Event Handlers

        /// <summary>
        /// Occurs when a property of the presented chord element has changed.
        /// </summary>
        /// <param name="sender">Object that has sent the event</param>
        /// <param name="e">Event data</param>
        private void presentedElement_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "Articulation")
                RaisePropertyChanged("Articulation");
        }

        /// <summary>
        /// Occurs when a property of this chord has changed.
        /// </summary>
        /// <param name="sender">Object that has sent the event</param>
        /// <param name="e">Event data</param>
        private void Chord_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "GroupBeam")
            {
                LayoutStem();
                LayoutNotes();
                LayoutDots();
            }
            else if (e.PropertyName == "StemDirection")
            {
                if (notesCollection.Count == 0)
                {
                    StemLength = 0.0;
                    return;
                }

                var staff = FindOwner<Staff>();
                var middlePosition = staff.Descriptor.Lines - 1;

                if (StemDirection == StemOrientation.Up)
                {
                    if (notesCollection.All(n => n.StaffPosition < -3))
                        StemLength = (middlePosition - notesCollection[0].StaffPosition) * (staff.SpaceSize / 2);
                    else
                    {
                        var notesDistance = notesCollection[notesCollection.Count - 1].StaffPosition - notesCollection[0].StaffPosition;
                        if (notesDistance == 0)
                            StemLength = 3.5 * staff.SpaceSize;
                        else
                            StemLength = (notesDistance * 0.5 + 3) * staff.SpaceSize;

                        if (BasicDuration == DurationalSymbolElement.ThirtySecond)
                            StemLength += 0.5 * staff.SpaceSize;
                        else if (BasicDuration == DurationalSymbolElement.SixtyFourth)
                            StemLength += staff.SpaceSize;
                        else if (BasicDuration == DurationalSymbolElement.HundredTwentyEighth)
                            StemLength += 1.5 * staff.SpaceSize;
                    }
                }
                else
                {
                    if (notesCollection.All(n => n.StaffPosition > staff.Descriptor.Lines + 3))
                        StemLength = (notesCollection[notesCollection.Count - 1].StaffPosition - middlePosition) * (staff.SpaceSize / 2);
                    else
                    {
                        var notesDistance = notesCollection[notesCollection.Count - 1].StaffPosition - notesCollection[0].StaffPosition;
                        StemLength = (notesDistance * 0.5 + 3) * staff.SpaceSize;

                        if (BasicDuration == DurationalSymbolElement.ThirtySecond)
                            StemLength += 0.5 * staff.SpaceSize;
                        else if (BasicDuration == DurationalSymbolElement.SixtyFourth)
                            StemLength += staff.SpaceSize;
                        else if (BasicDuration == DurationalSymbolElement.HundredTwentyEighth)
                            StemLength += 1.5 * staff.SpaceSize;
                    }
                }
            }
        }

        /// <summary>
        /// Occurs when the collection of tones of the presented chord has changed.
        /// </summary>
        /// <remarks>
        /// <b>Operation:</b><br/>
        /// The handler modifies the content of the <see cref="Notes"/> collection to reflect
        /// the current state of the presented tones collection.
        /// </remarks>
        /// <param name="sender">Object that has sent the event</param>
        /// <param name="e">Event data</param>
        private void Tones_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            switch (e.Action)
            {
                case NotifyCollectionChangedAction.Add:
                {
                    var currentEnvironment = FindOwner<Staff>().GetEnvironmentFor(this);

                    foreach (ToneElement tone in e.NewItems)
                    {
                        var note = new Note(PackagePart, this, tone, currentEnvironment);
                        note.PropertyChanged += new PropertyChangedEventHandler(note_PropertyChanged);
                        int i = 0;
                        for (; i < notesCollection.Count && notesCollection[i].PresentedTone < tone; ++i) ;
                        notesCollection.Insert(i, note);
                    }
                    break;
                }
                case NotifyCollectionChangedAction.Remove:
                {
                    foreach (ToneElement tone in e.OldItems)
                    {
                        var noteIndex = notesCollection.FindIndex(n => n.PresentedTone == tone);
                        notesCollection[noteIndex].PropertyChanged -= note_PropertyChanged;
                        notesCollection.RemoveAt(noteIndex);
                    }
                    break;
                }
            }

            LayoutStem();
            LayoutNotes();
            LayoutDots();
        }

        /// <summary>
        /// Occurs when some note has changed.
        /// </summary>
        /// <param name="sender">Object that has sent the event</param>
        /// <param name="e">Event data</param>
        private void note_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "StaffPosition" && !lockNotesCollection)
            {
                var note = sender as Note;
                int oldIndex = notesCollection.IndexOf(note);
                int newIndex = notesCollection.Count(n => n.StaffPosition < note.StaffPosition);
                if (newIndex != oldIndex)
                    notesCollection.Move(oldIndex, newIndex);

                LayoutStem();
                LayoutNotes();
                LayoutDots();
            }
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Gets the size (in centimeters) of given chord parts.
        /// </summary>
        /// <param name="parts">
        /// <para>
        /// Type: <see cref="ChordPart"/>
        /// </para>
        /// <para>
        /// The desired chord parts (bitwise or of <see cref="ChordPart"/> values).
        /// </para>
        /// </param>
        /// <returns>
        /// <para>
        /// Type: <see cref="Double"/>
        /// </para>
        /// <para>
        /// The size (in centimeters) of the desired chord parts.
        /// </para>
        /// </returns>
        public double GetSizes(ChordPart parts)
        {
            double width = 0.0;

            if ((parts & ChordPart.Accidentals) > 0)
            {
                var accidentals = from note in notesCollection
                                  where note.Accidental.Type != AccidentalType.None
                                  select note.Accidental;
                if (accidentals.Count() > 0)
                {
                    double left = accidentals.Min(a => a.Position);
                    double right = accidentals.Max(a => a.Position + a.Width);

                    width += right - left;
                }
            }

            if ((parts & ChordPart.AccidentalsOffset) > 0)
            {
                var accidentals = from note in notesCollection
                                  where note.Accidental.Type != AccidentalType.None
                                  select note.Accidental;
                if (accidentals.Count() > 0)
                {
                     double right = accidentals.Max(a => a.Position + a.Width);
                     width += -right - GetSizes(ChordPart.LeftNotes);
                }
            }

            if ((parts & ChordPart.LeftNotes) > 0)
            {
                var notes = from note in notesCollection
                            where note.HeadPosition == NotePosition.Left
                            select note;

                if (notes.Count() > 0)
                    width += notes.Max(n => n.Width);
            }

            if ((parts & ChordPart.RightPart) > 0)
            {
                double notesWidth = 0.0, flagsWidth = 0.0;

                var notes = from note in notesCollection
                            where note.HeadPosition == NotePosition.Right
                            select note;

                if (notes.Count() > 0)
                    notesWidth = notes.Max(n => n.Width);
                if (BasicDuration <= DurationalSymbolElement.Eighth)
                {
                    var staffSpaces = FindOwner<Staff>().Descriptor.Lines - 1;
                    double baseSize;
                    if (staffSpaces > 0)
                        baseSize = EffectiveTypeSize / (staffSpaces);
                    else
                        baseSize = FindOwner<Staff>().SpaceSize;

                    flagsWidth = 0.75 * baseSize;
                }

                width += Math.Max(notesWidth, flagsWidth);
            }
            else
            {
                if ((parts & ChordPart.RightNotes) > 0)
                {
                    var notes = from note in notesCollection
                                where note.HeadPosition == NotePosition.Right
                                select note;

                    if (notes.Count() > 0)
                        width += notes.Max(n => n.Width);
                }

                if ((parts & ChordPart.Flags) > 0)
                {
                    if (BasicDuration <= DurationalSymbolElement.Eighth)
                    {
                        var staffSpaces = FindOwner<Staff>().Descriptor.Lines - 1;
                        double baseSize;
                        if (staffSpaces > 0)
                            baseSize = EffectiveTypeSize / (staffSpaces);
                        else
                            baseSize = FindOwner<Staff>().SpaceSize;

                        width += 0.75 * baseSize;
                    }
                }
            }

            return width;
        }

        /// <summary>
        /// Gets the position of the stem's end point.
        /// </summary>
        /// <returns>
        /// <para>
        /// Type: <see cref="Double"/>
        /// </para>
        /// <para>
        /// The offset (in centimeters) of the stem's end point
        /// from the bottom of the staff.
        /// </para>
        /// </returns>
        public double GetStemEnd()
        {
            if (notesCollection.Count == 0)
                return 0.0;

            return (StemDirection == StemOrientation.Up ? GetStemStart() - StemLength : GetStemStart() + StemLength);
        }

        /// <summary>
        /// Gets the position of the stem's starting point.
        /// </summary>
        /// <returns>
        /// <para>
        /// Type: <see cref="Double"/>
        /// </para>
        /// <para>
        /// The offset (in centimeters) of the stem's start point
        /// from the bottom of the staff.
        /// </para>
        /// </returns>
        public double GetStemStart()
        {
            if (notesCollection.Count == 0)
                return 0.0;

            int stemStartPos = (StemDirection == StemOrientation.Up ? LowestNote.StaffPosition : HighestNote.StaffPosition);
            var staff = FindOwner<Staff>();
            
            return -stemStartPos * (staff.SpaceSize / 2);
        }

        /// <summary>
        /// Computes the position of augmentation dots for each note in the chord.
        /// </summary>
        public void LayoutDots()
        {
            if (notesCollection.Count == 0)
                return;

            int[] dots = new int[notesCollection.Count];

            int lastDotPosition = notesCollection[0].StaffPosition - 2; // The dot won't collide with any other note

            for (int i = 0; i < notesCollection.Count; ++i)
            {
                int dotPosition = notesCollection[i].StaffPosition;

                if (dotPosition % 2 == 0)
                    ++dotPosition;
                if (dotPosition <= lastDotPosition)
                    dotPosition = lastDotPosition + 2;

                dots[i] = dotPosition;
                lastDotPosition = dotPosition;
            }

            while (dots[notesCollection.Count - 1] - notesCollection[notesCollection.Count - 1].StaffPosition > notesCollection[0].StaffPosition - dots[0] + 2)
            {
                for (int i = 0; i < notesCollection.Count; ++i)
                    dots[i] -= 2;
            }

            for (int i = 0; i < notesCollection.Count; ++i)
                notesCollection[i].DotsPosition = dots[i] - notesCollection[i].StaffPosition;
        }

        /// <summary>
        /// Computes the position of the notes in the <see cref="Notes"/> collection.
        /// </summary>
        /// <remarks>
        /// <b>The algorithm:</b><br/>
        /// The algorithm starts at the base note (i.e. the one where the stem starts)
        /// and places it accordingly to the <see cref="StemDirection"/>
        /// (For a stem facing <see cref="StemOrientation.Up"/>, the position will be <see cref="NotePosition.Left"/> and vice versa).
        /// This position is the base position.
        /// Then the algorithm iterates to the other end of the <see cref="Notes"/> collection and places the notes in the base position
        /// if it is possible. If it is not (the positioned note is just one second (half staff space) from the previous one that was
        /// placed in the base position), the note is placed in the opposite position.
        /// </remarks>
        public void LayoutNotes()
        {
            if (notesCollection.Count == 0)
                return;

            if (StemDirection == StemOrientation.Down)
            {
                notesCollection[notesCollection.Count - 1].HeadPosition = NotePosition.Right;
                int lastPos = notesCollection[notesCollection.Count - 1].StaffPosition;
                NotePosition lastHeadPos = NotePosition.Right;

                for (int i = notesCollection.Count - 2; i >= 0; --i)
                {
                    if (notesCollection[i].StaffPosition == lastPos - 1 && lastHeadPos == NotePosition.Right)
                        notesCollection[i].HeadPosition = NotePosition.Left;
                    else
                        notesCollection[i].HeadPosition = NotePosition.Right;
                    
                    lastPos = notesCollection[i].StaffPosition;
                    lastHeadPos = notesCollection[i].HeadPosition;
                }
            }
            else
            {
                notesCollection[0].HeadPosition = NotePosition.Left;
                int lastPos = notesCollection[0].StaffPosition;
                NotePosition lastHeadPos = NotePosition.Left;

                for (int i = 0; i < notesCollection.Count; ++i)
                {
                    if (notesCollection[i].StaffPosition == lastPos + 1 && lastHeadPos == NotePosition.Left)
                        notesCollection[i].HeadPosition = NotePosition.Right;
                    else
                        notesCollection[i].HeadPosition = NotePosition.Left;

                    lastPos = notesCollection[i].StaffPosition;
                    lastHeadPos = notesCollection[i].HeadPosition;
                }
            }

            Layout.Layouter.LayoutAccidentals(notesCollection.Reverse());
        }

        /// <summary>
        /// Computes the length and direction of the stem and layouts it.
        /// </summary>
        /// <remarks>
        /// <b>The Algorithm:</b><br/>
        /// <para>
        /// <i>The Stem Direction:</i><br/>
        /// The orientation of the stem is given by the note with the greatest distance from the staff position
        /// given as <see cref="StaffDescriptor.Lines"/> - 1 (which gives 3rd line for a 5 lines staff).
        /// If this note is above or on that position, the stem points <see cref="StemOrientation.Down"/>, otherwise it is oriented
        /// <see cref="StemOrientation.Up"/>.
        /// </para>
        /// <para>
        /// <i>The Stem Length:</i><br/>
        /// If all the notes from the <see cref="Notes"/> collection are placed on the ledger lines, the stem will be drawn
        /// from the further note to the middle staff line. Otherwise, it will end 3/2 staff spaces from the nearest note.<br/>
        /// "Further" and "nearest" are defined regarding the <see cref="StemDirection"/>. If it is <see cref="StemOrientation.Up"/>
        /// the further is the bottom (lowest) note and vice versa.
        /// </para>
        /// </remarks>
        public void LayoutStem()
        {
            if (notesCollection.Count == 0)
            {
                StemDirection = StemOrientation.Up;
                StemLength = 0.0;
                return;
            }

            var staff = FindOwner<Staff>();

            if (GroupBeam != null)
            {
                var xDistance = GroupBeam.BeamedSymbols[GroupBeam.BeamedSymbols.Count - 1].Position - GroupBeam.BeamedSymbols[0].Position;
                if (xDistance == 0)
                    return;
                var yDistance = GroupBeam.Y2 - GroupBeam.Y1;
                var t = (Position - GroupBeam.BeamedSymbols[0].Position) / xDistance;
                var y = GroupBeam.Y1 + t * yDistance;
                var verticalPosition = -notesCollection[0].StaffPosition * (staff.SpaceSize / 2);

                if (verticalPosition < y)
                {
                    StemDirection = StemOrientation.Down;
                    var highestPosition = -notesCollection[notesCollection.Count - 1].StaffPosition * (staff.SpaceSize / 2);
                    StemLength = y - highestPosition;
                }
                else
                {
                    StemDirection = StemOrientation.Up;
                    StemLength = verticalPosition - y;
                }

                return;
            }

            var middlePosition = staff.Descriptor.Lines - 1;
            var down = notesCollection[notesCollection.Count - 1].StaffPosition - middlePosition;
            var up = middlePosition - notesCollection[0].StaffPosition;

            if (up > down)
            {
                StemDirection = StemOrientation.Up;
                if (notesCollection.All(n => n.StaffPosition < -3))
                    StemLength = (middlePosition - notesCollection[0].StaffPosition) * (staff.SpaceSize / 2);
                else
                {
                    var notesDistance = notesCollection[notesCollection.Count - 1].StaffPosition - notesCollection[0].StaffPosition;
                    if (notesDistance == 0)
                        StemLength = 3.5 * staff.SpaceSize;
                    else
                        StemLength = (notesDistance * 0.5 + 3) * staff.SpaceSize;

                    if (BasicDuration == DurationalSymbolElement.ThirtySecond)
                        StemLength += 0.5 * staff.SpaceSize;
                    else if (BasicDuration == DurationalSymbolElement.SixtyFourth)
                        StemLength += staff.SpaceSize;
                    else if (BasicDuration == DurationalSymbolElement.HundredTwentyEighth)
                        StemLength += 1.5 * staff.SpaceSize;
                }
            }
            else
            {
                StemDirection = StemOrientation.Down;
                if (notesCollection.All(n => n.StaffPosition > staff.Descriptor.Lines + 3))
                    StemLength = (notesCollection[notesCollection.Count - 1].StaffPosition - middlePosition) * (staff.SpaceSize / 2);
                else
                {
                    var notesDistance = notesCollection[notesCollection.Count - 1].StaffPosition - notesCollection[0].StaffPosition;
                    StemLength = (notesDistance * 0.5 + 3) * staff.SpaceSize;

                    if (BasicDuration == DurationalSymbolElement.ThirtySecond)
                        StemLength += 0.5 * staff.SpaceSize;
                    else if (BasicDuration == DurationalSymbolElement.SixtyFourth)
                        StemLength += staff.SpaceSize;
                    else if (BasicDuration == DurationalSymbolElement.HundredTwentyEighth)
                        StemLength += 1.5 * staff.SpaceSize;
                }
            }

            if (BasicDuration >= DurationalSymbolElement.Whole)
                StemLength = 0.0;
        }

        /// <summary>
        /// Restores a given note.
        /// </summary>
        /// <remarks>
        /// This method is intended as a temporary solution to the scenario when the user redoes
        /// a previously undone note insertion operation.
        /// </remarks>
        /// <param name="note">
        /// <para>
        /// Type: <see cref="Note"/>
        /// </para>
        /// <para>
        /// The restored note.
        /// </para>
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// The <paramref name="note"/> is <see langword="null"/>.
        /// </exception>
        /// <exception cref="ArgumentException">
        /// The <paramref name="note"/> does not belong to this chord.<br/>
        /// -or-<br/>
        /// The chord does not contain any note that could be replaced by the restored <paramref name="note"/>.
        /// </exception>
        public void RestoreNote(Note note)
        {
            if (note == null)
                throw new ArgumentNullException("note");
            if (note.FindOwner<Chord>() != this)
                throw new ArgumentException("The note does not belong to this chord.", "note");

            var replaced = notesCollection.FirstOrDefault(n => n.PresentedTone == note.PresentedTone);
            if (replaced == null)
                throw new ArgumentException("The chord does not contain any note that could be replaced by the restored note.", "note");
            replaced.PropertyChanged -= note_PropertyChanged;
            notesCollection[notesCollection.IndexOf(replaced)] = note;
            note.PropertyChanged += new PropertyChangedEventHandler(note_PropertyChanged);
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets or sets the articulation that this chord should be performed with.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="ArticulationType"/>
        /// </para>
        /// <para>
        /// The articulation that this chord should be performed with.
        /// </para>
        /// </value>
        public ArticulationType Articulation
        {
            get { return ((ChordElement)PresentedElement).Articulation; }
            set { ((ChordElement)PresentedElement).Articulation = value; }
        }

        /// <summary>
        /// Gets the highest note in the chord.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="Note"/>
        /// </para>
        /// <para>
        /// The highest note in the chord. This property can be used for layouting.
        /// </para>
        /// </value>
        public Note HighestNote
        {
            get
            {
                if (notesCollection.Count == 0)
                    return null;

                return notesCollection[notesCollection.Count - 1];
            }
        }

        /// <summary>
        /// Gets the highest tone in the chord.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="ToneElement"/>
        /// </para>
        /// <para>
        /// The highest tone in the chord. This property can be used for ordering.
        /// </para>
        /// </value>
        public ToneElement HighestTone
        {
            get 
            {
                if (notesCollection.Count == 0)
                    return null;

                return notesCollection[notesCollection.Count - 1].PresentedTone;
            }
        }

        /// <summary>
        /// Gets the lowest note in the chord.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="Note"/>
        /// </para>
        /// <para>
        /// The lowest note in the chord. This property can be used for layouting.
        /// </para>
        /// </value>
        public Note LowestNote
        {
            get
            {
                if (notesCollection.Count == 0)
                    return null;

                return notesCollection[0];
            }
        }

        /// <summary>
        /// Gets the lowest tone in the chord.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="ToneElement"/>
        /// </para>
        /// <para>
        /// The lowest tone in the chord. This property can be used for ordering.
        /// </para>
        /// </value>
        public ToneElement LowestTone
        {
            get
            {
                if (notesCollection.Count == 0)
                    return null;

                return notesCollection[0].PresentedTone;
            }
        }

        /// <summary>
        /// Gets the collection of notes belonging to this chord.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="ReadOnlyCollection{T}"/> of <see cref="Note"/>
        /// </para>
        /// <para>
        /// The 
        /// </para>
        /// </value>
        public ReadOnlyCollection<Note> Notes
        {
            get { return notes; }
        }

        /// <summary>
        /// Gets or sets the direction of the stem.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="StemOrientation"/>
        /// </para>
        /// <para>
        /// The direction of the <see cref="Chord"/>'s stem.
        /// </para>
        /// </value>
        public StemOrientation StemDirection
        {
            get { return (StemOrientation)Enum.Parse(typeof(StemOrientation), stemDirectionAttribute.Value); }
            set
            {
                stemDirectionAttribute.Value = value.ToString();
                LayoutNotes();

                RaisePropertyChanged("StemDirection");
            }
        }

        /// <summary>
        /// Gets or sets the length of the stem.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="Double"/>
        /// </para>
        /// <para>
        /// The length of the <see cref="Chord"/>'s stem.
        /// </para>
        /// </value>
        /// <exception cref="ValidationException">
        /// The <paramref name="value"/> is less than 0 or equal to <see cref="Double.NaN"/>.
        /// </exception>
        public double StemLength
        {
            get { return double.Parse(stemLengthAttribute.Value, PackageVocabulary.PackageCulture.NumberFormat); }
            set
            {
                ValidateAndThrow("StemLength", value);

                stemLengthAttribute.Value = value.ToString(PackageVocabulary.PackageCulture.NumberFormat);
                RaisePropertyChanged("StemLength");
            }
        }

        #endregion

        #region Private Fields

        /// <summary>
        /// Indicates whether the changes into notes collection are allowed;
        /// </summary>
        private bool lockNotesCollection = false;
        /// <summary>
        /// Holds the value of the <see cref="Notes"/> property.
        /// </summary>
        private ReadOnlyCollection<Note> notes;
        /// <summary>
        /// Holds the collection of the notes.
        /// </summary>
        private ElementCollection<Note> notesCollection;
        /// <summary>
        /// Holds the XML element that holds the <see cref="notesCollection"/>.
        /// </summary>
        private XmlElement notesElement;
        /// <summary>
        /// Holds the XML attribute that holds the value of the <see cref="StemDirection"/> property.
        /// </summary>
        private XmlAttribute stemDirectionAttribute;
        /// <summary>
        /// Holds the XML attribute that holds the value of the <see cref="StemLength"/> property.
        /// </summary>
        private XmlAttribute stemLengthAttribute;

        #endregion
    }

    /// <summary>
    /// Defines a measured part of a chord.
    /// </summary>
    [Flags]
    public enum ChordPart
    {
        /// <summary>
        /// The accidentals (from the leftmost to the rightmost one).
        /// </summary>
        Accidentals = 1,
        /// <summary>
        /// The notes that have their head on the <see cref="NotePosition.Left"/> side.
        /// </summary>
        LeftNotes = 2,
        /// <summary>
        /// The notes that have their head on the <see cref="NotePosition.Right"/> side.
        /// </summary>
        RightNotes = 4,
        /// <summary>
        /// The flags.
        /// </summary>
        Flags = 8,
        /// <summary>
        /// From the rightmost accidental to the chord's stem.
        /// </summary>
        AccidentalsOffset = 16,
        /// <summary>
        /// The notes.
        /// </summary>
        Notes = LeftNotes | RightNotes,
        /// <summary>
        /// The left part of the chord (from the leftmost accidental to the stem).
        /// </summary>
        LeftPart = LeftNotes | Accidentals,
        /// <summary>
        /// The right part of the chord (from the stem to the right, but without the augmentation dots).
        /// </summary>
        RightPart = RightNotes | Flags,
        /// <summary>
        /// The whole chord (without the augmentation dots).
        /// </summary>
        All = LeftPart | RightPart
    }

    /// <summary>
    /// Determines the orientation of a <see cref="Chord"/>'s (note's) stem.
    /// </summary>
    public enum StemOrientation
    {
        /// <summary>
        /// The stem points up.
        /// </summary>
        Up,
        /// <summary>
        /// The stem points down.
        /// </summary>
        Down
    }
}
